package com.orhanobut.hawk

import com.google.gson.Gson
import com.orhanobut.hawk.converter.Converter
import com.orhanobut.hawk.converter.HawkConverter
import com.orhanobut.hawk.encryption.Encryption
import com.orhanobut.hawk.encryption.NoEncryption
import com.orhanobut.hawk.log.EmptyLogger
import com.orhanobut.hawk.log.LogInterceptor
import com.orhanobut.hawk.parser.GsonParser
import com.orhanobut.hawk.serializer.HawkSerializer
import com.orhanobut.hawk.serializer.Serializer
import com.orhanobut.hawk.storage.Storage

class Hawk(
    private val storage: Storage,
    private val logInterceptor: LogInterceptor = EmptyLogger(),
    private val converter: Converter = HawkConverter(GsonParser(Gson())),
    private val encryption: Encryption = NoEncryption(),
    private val serializer: Serializer = HawkSerializer(logInterceptor)
) {
    fun <T> put(key: String, value: T?): Boolean {
        if (key.isEmpty()) {
            logInterceptor.w("Key is empty")
            return false
        }
        if (value == null) return delete(key)

        val plainText = converter.toString(value)
        if (plainText == null) {
            logInterceptor.e("Converter failed")
            return false
        }

        val cipherText = try {
            encryption.encrypt(key, plainText)
        } catch (e: Exception) {
            logInterceptor.e(e.message ?: "encrypt failed")
            return false
        }

        val serializedText = serializer.serialize(cipherText, value)
        return run {
            val ok = storage.put(key, serializedText)
            if (ok) logInterceptor.i("Stored successfully") else logInterceptor.e("Store operation failed")
            ok
        }
    }

    fun <T> get(key: String): T? {
        if (key.isEmpty()) {
            logInterceptor.w("Key is empty")
            return null
        }
        if (!contains(key)) logInterceptor.i("Key not exists: $key")

        val serializedText = storage.get<String>(key)
        if (serializedText == null) {
            logInterceptor.e("Fetching from storage failed")
            return null
        }

        val dataInfo = try {
            serializer.deserialize(serializedText)
        } catch (e: Exception) {
            logInterceptor.e("Deserialization failed")
            return null
        }

        val plainText = try {
            encryption.decrypt(key, dataInfo.cipherText)
        } catch (e: Exception) {
            logInterceptor.e("Decrypt failed")
            return null
        }

        return try {
            val result = converter.fromString<T>(plainText, dataInfo)
            result
        } catch (e: Exception) {
            logInterceptor.e("Converter failed")
            null
        }
    }

    fun <T> get(key: String, defaultValue: T): T {
        val v: T? = get(key)
        return v ?: defaultValue
    }

    fun <T> serialize(value: T): String {
        val plainText = converter.toString(value)
        if (plainText == null) {
            logInterceptor.e("Converter failed")
            return ""
        }
        val serializedText = serializer.serialize(plainText, value)
        return serializedText
    }

    fun <T> deserialize(text: String): T? {
        val dataInfo = try {
            serializer.deserialize(text)
        } catch (e: Exception) {
            logInterceptor.e("Deserialization failed")
            return null
        }
        return try {
            converter.fromString<T>(dataInfo.cipherText, dataInfo)
        } catch (e: Exception) {
            logInterceptor.e("Converter failed")
            null
        }
    }

    fun count(): Long = storage.count()
    fun deleteAll(): Boolean =
        storage.deleteAll().also { if (it) logInterceptor.i("Cleared all") else logInterceptor.e("Clear failed") }

    fun delete(key: String): Boolean = storage.delete(key)
        .also { if (it) logInterceptor.i("Deleted: $key") else logInterceptor.e("Delete failed: $key") }

    fun contains(key: String): Boolean = storage.contains(key)
    fun destroy() {
        storage.destroy()
    }
}
